﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ViewMaker.Core.ViewModels;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Documents;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;
using System.Diagnostics;
using System.Windows.Markup;
using ViewMaker.Core.Utils;
using System.Windows.Media.Imaging;

namespace ViewMaker.Core.Wpf
{
    internal class WpfViewLayoutViewConnector : DispatcherObject
    {
        internal class MarkerAdorner : Adorner
        {
            public MarkerAdorner(UIElement adornedElement)
                : base(adornedElement)
            {
            }

            protected override void OnRender(DrawingContext drawingContext)
            {
                Rect adornedElementRect = new Rect(this.AdornedElement.RenderSize);

                Pen renderPen = new Pen(new SolidColorBrush(Colors.Yellow), 3.0);
                drawingContext.DrawRectangle(null, renderPen, adornedElementRect);
            }
        }

        private ViewLayoutViewModel editor;
        private IView view;
        private ContentControl content;
        private bool isLayoutActive;

        public WpfViewLayoutViewConnector(ContentControl parent, ViewLayoutViewModel editor)
        {
            this.editor = editor;
            this.content = parent;

            if (Application.Current != null && !handledDispatcherUnhandledExceptionHandler)
            {
                handledDispatcherUnhandledExceptionHandler = true;
                Application.Current.DispatcherUnhandledException += new DispatcherUnhandledExceptionEventHandler(DispatcherUnhandledExceptionHandler);
            }
            if (content.IsLoaded)
            {
                ConnectEditor();
            }
            else
            {
                content.Loaded += delegate { ConnectEditor(); };
            }

        }

        private StackPanel CreateContentMenuItem(string uri, string text)
        {
            var item = new StackPanel { Orientation = Orientation.Horizontal };
            item.Margin = new Thickness(5);
            item.Children.Add(new Image { Source = new BitmapImage(new Uri("/ViewMaker.Core;component/Images/" + uri, UriKind.Relative)) });
            item.Children.Add(new TextBlock { Text = text });
            return item;
        }

        private void ConnectEditor()
        {
            content.ContextMenu = new ContextMenu();
            var item1 = new MenuItem() { Header = CreateContentMenuItem("edit.png", "Edit Layout") };
            content.ContextMenu.Items.Add(item1);
            item1.Command = new RelayCommand(delegate() { this.view.Show(); this.editor.Activate(); isLayoutActive = true; });

            content.ContextMenu.Items.Add(new Separator());

            var item2 = new MenuItem() { Header = "Show XAML" };
            content.ContextMenu.Items.Add(item2);
            item2.Command = editor.ShowXamlCommand;

            var item3 = new MenuItem() { Header = "Show Layout" };
            content.ContextMenu.Items.Add(item3);
            item3.Command = editor.ShowLayoutViewerCommand;

            this.view = ViewUtil.BuildView(editor, false);

            content.Unloaded += new RoutedEventHandler(Content_Unloaded);
            content.GotKeyboardFocus += new KeyboardFocusChangedEventHandler(Content_GotKeyboardFocus);
            editor.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(ViewModel_PropertyChanged);
            editor.ViewCommandNotified += new EventHandler<ViewCommandEventArgs>(ViewModel_ViewCommandNotified);
        }

        void Content_Unloaded(object sender, RoutedEventArgs e)
        {
            editor.Close();
            content.Unloaded -= new RoutedEventHandler(Content_Unloaded);
            content.GotKeyboardFocus -= new KeyboardFocusChangedEventHandler(Content_GotKeyboardFocus);
            editor.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler(ViewModel_PropertyChanged);
            editor.ViewCommandNotified -= new EventHandler<ViewCommandEventArgs>(ViewModel_ViewCommandNotified);
        }

        private static bool handledDispatcherUnhandledExceptionHandler;
        private static void DispatcherUnhandledExceptionHandler(object sender, DispatcherUnhandledExceptionEventArgs e)
        {
            if (e.Handled) return;
            if (MessageBox.Show(e.Exception.Message + "\n\n" + "Press OK to continue.", "Error", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
            {
                e.Handled = true;
            }
        }

        void Content_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
        {
            if (isLayoutActive) SyncViewModelTree(e.OriginalSource as DependencyObject);
        }

        private void SyncViewModelTree(DependencyObject obj)
        {
            if (obj == null) return;
            var fe = obj as FrameworkElement;
            if (fe != null && fe.Name != null)
            {
                if (editor.SelectByName(fe.Name)) return;
            }
            if (obj is Visual)
            {
                SyncViewModelTree(VisualTreeHelper.GetParent(obj));
            }
            else if (obj is FrameworkContentElement)
            {
                SyncViewModelTree(((FrameworkContentElement)obj).Parent);
            }
        }


        void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "CurrentItem") UpdateMarker(editor.CurrentItem);
            if (e.PropertyName == "XamlCode") RefreshTarget();
        }

        void ViewModel_ViewCommandNotified(object sender, ViewCommandEventArgs e)
        {
            if (e.Command == "Hide")
            {
                isLayoutActive = false;
                UpdateMarker(null);
            }
            else if (e.Command == "GetElement")
            {
                var para = e.Parameter as ViewLayoutViewModel.GetElementCommandInfo;
                if (para != null) para.Element = FindById(content, para.Name);
            }

        }


        #region Adorner・Canvas関連

        private AdornerLayer myAdornerLayer;
        private MarkerAdorner preAdorner;
        private Thumb preThumb;

        private delegate void UpdatMakerDelegate(ViewLayoutItem current);

        private void UpdateMarker(ViewLayoutItem current)
        {
            if (myAdornerLayer != null) myAdornerLayer.Remove(preAdorner);
            if (preThumb != null) preThumb.Visibility = System.Windows.Visibility.Collapsed;
            myAdornerLayer = null;
            this.editor.InfoText = "";
            if (current != null)
            {
                var ele = FindById(content, current.Name);
                if (ele != null && preAdorner != ele)
                {
                    MakeAdorner(ele);
                    MakeInfoText(ele);
                    if (current.Control.IsCanvasElement) MakeCanvasThumb(ele);
                }
            }
        }

        private void MakeAdorner(FrameworkElement ele)
        {
            myAdornerLayer = AdornerLayer.GetAdornerLayer(ele);
            if (myAdornerLayer != null)
            {
                preAdorner = new MarkerAdorner(ele);
                myAdornerLayer.Add(preAdorner);
            }
        }

        private void MakeInfoText(FrameworkElement ele)
        {
            this.editor.InfoText = string.Format("SIZE: {0} x {1}",
                double.IsNaN(ele.ActualWidth) ? "*" : ele.ActualWidth.ToString("0.0"),
                double.IsNaN(ele.ActualHeight) ? "*" : ele.ActualHeight.ToString("0.0"));
        }

        private void MakeCanvasThumb(FrameworkElement ele)
        {
            Canvas canvas = null;
            FrameworkElement target = ele;
            DependencyObject obj = ele.Parent;

            while (obj != null)
            {
                if (obj is Canvas)
                {
                    canvas = (Canvas)obj;
                    break;
                }
                if (obj is FrameworkElement)
                {
                    target = (FrameworkElement)obj;
                }
                obj = VisualTreeHelper.GetParent(obj);
            } 
            if (canvas != null)
            {
                string thumbName = canvas.Name + "__CanvasThumb";
                var thumb = (Thumb)FindById(content, thumbName);
                if (thumb == null)
                {
                    thumb = new Thumb() { Name = thumbName, Width = 10, Height = 10, Background = Brushes.Yellow };
                    canvas.Children.Add(thumb);
                    thumb.DragDelta += new DragDeltaEventHandler(thumb_DragDelta);
                }
                thumb.Visibility = System.Windows.Visibility.Visible;
                Canvas.SetTop(thumb, Canvas.GetTop(target) + target.ActualHeight- thumb.Height);
                Canvas.SetLeft(thumb, Canvas.GetLeft(target) + target.ActualWidth - thumb.Width);
                preThumb = thumb;
                preThumb.Tag = new List<FrameworkElement>(new FrameworkElement[] { target });;
            }
        }

        void thumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            var target = (Thumb)e.Source;
            Canvas.SetLeft(target, Canvas.GetLeft(target) + e.HorizontalChange);
            Canvas.SetTop(target, Canvas.GetTop(target) + e.VerticalChange);
            var list = (List<FrameworkElement>)target.Tag;
            foreach (var ele in list)
            {
                Canvas.SetLeft(ele, Canvas.GetLeft(ele) + e.HorizontalChange);
                Canvas.SetTop(ele, Canvas.GetTop(ele) + e.VerticalChange);
            }
            editor.CurrentItem.Control.CanvasLeft = (int)Canvas.GetLeft(list.First());
            editor.CurrentItem.Control.CanvasTop = (int)Canvas.GetTop(list.First());
        }

        private FrameworkElement FindById(FrameworkElement target, string name)
        {
            return target.FindById(name);
        }


        private void RefreshTarget()
        {
            UpdateMarker(null);
            var xamlString = this.editor.XamlCode;
            try
            {
                var root = xamlString == null ? null : (FrameworkElement)XamlReader.Parse(xamlString);
                content.Content = root;
            }
            catch (XamlParseException ex)
            {
                Debug.WriteLine(ex.ToString());
                Debug.WriteLine(xamlString);

                var line = ex.LineNumber > 0 ? xamlString.Split('\n').Skip(ex.LineNumber-1).First() : "";
                content.Content = ex.Message + "\n" + line;
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
                Debug.WriteLine(xamlString);
                content.Content = ex.Message;
            }

            this.Dispatcher.BeginInvoke(new UpdatMakerDelegate(UpdateMarker), DispatcherPriority.Background, editor.CurrentItem); 
            var win = content as Window;
            if (win != null) win.Activate();

            //DataGridでDeferRefresh Exceptionが出る問題の対処は以下の方法になるが本ツールでは特別な対処しない
            //https://connect.microsoft.com/VisualStudio/feedback/details/532494/wpf-datagrid-and-tabcontrol-deferrefresh-exception
        }

        #endregion

    }
}
